package ru.ipo.dces.server.http;

import ru.ipo.dces.clientservercommunication.*;
import ru.ipo.dces.exceptions.GeneralRequestFailureException;
import ru.ipo.dces.exceptions.ServerReturnedError;
import ru.ipo.dces.log.LogMessageType;
import ru.ipo.dces.log.LoggerFactory;
import ru.ipo.dces.server.RequestResponseMapping;
import ru.ipo.dces.server.ServerFacade;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.util.Arrays;

public class HttpServer implements ServerFacade {

    private final String URL_string;
    private final Proxy proxy;
    private final byte[] REQUEST_VAR = "x=".getBytes(PHP.SERVER_CHARSET);

    public HttpServer(String URL_string) {
        this.URL_string = URL_string;
        this.proxy = null;
    }

    public HttpServer(String URL_string, Proxy proxy) {
        this.URL_string = URL_string;
        this.proxy = proxy;
    }

    public InputStream doPost(Object sendToServer) throws Exception {
        HttpURLConnection con;
        if (proxy == null)
            con = (HttpURLConnection) new URL(URL_string).openConnection();
        else
            con = (HttpURLConnection) new URL(URL_string).openConnection(proxy);
        con.setDoInput(true);
        con.setUseCaches(false);

        con.setRequestMethod("POST");
        con.setDoOutput(true);

        final OutputStream out = con.getOutputStream();
        //print request
        out.write(REQUEST_VAR);
        PHP.serialize(sendToServer, out);
        out.close();

        // Read answer
        InputStream in = con.getInputStream();
        if (con.getResponseCode() != HttpURLConnection.HTTP_OK)
            throw new ConnectException("Соединение с " + URL_string + " не удалось!");

        return in;
    }

    public <T extends Response> T doRequest(Class<T> cls, Request obj)
            throws ServerReturnedError, GeneralRequestFailureException {

        InputStream input;
        RequestFailedResponse failedResponse;

        try {
            input = doPost(obj);
            input = new BufferedInputStream(input, 4096);
            input.mark(4096);
        } catch (Exception e) {
            LoggerFactory.getLogger().log("Не удалось соединиться с сервером (ошибка в system.err)", LogMessageType.Error, null);
            e.printStackTrace();
            throw new GeneralRequestFailureException();
        }

        try {
            failedResponse = PHP.unserialize(RequestFailedResponse.class, input);
            if (failedResponse == null)
                return PHP.unserialize(cls, input);
        } catch (Exception e) {
            LoggerFactory.getLogger().log("Произошла ошибка связи с сервером", LogMessageType.Error, null);
            try {
                byte[] actualAnswer;
                input.reset();
                actualAnswer = inputStreamToByteArray(0, input);
                System.err.println("actual server answer:\n" + new String(actualAnswer, PHP.SERVER_CHARSET));
            } catch (IOException ioe) {
                System.err.println("failed to get actual server answer");
            }

            throw new GeneralRequestFailureException();
        }

        //now failedResponse != null
        switch (failedResponse.failReason) {
            case BrokenServerError:
                LoggerFactory.getLogger().log(
                        "Ошибка на стороне сервера №" + failedResponse.failErrNo +
                                (failedResponse.extendedInfo == null ? "" : ". " + failedResponse.extendedInfo),
                        LogMessageType.Error,
                        null
                );
                throw new GeneralRequestFailureException();
            case BrokenServerPluginError:
                LoggerFactory.getLogger().log(
                        "Ошибка на стороне сервера №" + failedResponse.failErrNo +
                                (failedResponse.extendedInfo == null ? "" : ". " + failedResponse.extendedInfo),
                        LogMessageType.Error,
                        null
                );
                throw new GeneralRequestFailureException();
            case BusinessLogicError:
                throw new ServerReturnedError(failedResponse.failErrNo, failedResponse.extendedInfo);
        }

        LoggerFactory.getLogger().log("Неизвестная ошибка при попытке связи с сервером", LogMessageType.Error, null);
        throw new GeneralRequestFailureException();
    }

    private byte[] inputStreamToByteArray(int fromIndex, InputStream input) throws IOException {
        byte[] buf = new byte[4096];
        int n;
        for (int i = fromIndex; (n = input.read()) != -1; i++) {
            if (i >= buf.length) {
                //copy array
                buf = Arrays.copyOf(buf, buf.length << 1);
                if (buf.length > 4 * 1024 * 1024) throw new IOException("Too long input stream");
            }
            buf[i] = (byte) n;
        }
        return buf;
    }

    @Override
    public <T> T doRequest(Request r) throws ServerReturnedError, GeneralRequestFailureException {
        Class<? extends Response> responseClass = RequestResponseMapping.getResponseClass(r.getClass());

        return (T) doRequest(responseClass, r);
    }
}
